package uuid

import (
	"fmt"
	"math"
	"strings"
	"sync"
	"time"

	"gitee.com/doraemon1996/bunny/log"
)

const (
	Bits_NodeID    = 10 // 节点ID位数 1024个节点
	Bits_Timestamp = 33 // 时间戳(秒)所占位数
	Bits_Sequence  = 20 // 序列号 1048575个数

	Max_NodeID    = 1<<Bits_NodeID - 1    // 节点ID最大值
	Max_Timestamp = 1<<Bits_Timestamp - 1 // 时间戳最大值
	Max_Sequence  = 1<<Bits_Sequence - 1  // 序列号最大值

	Shift_NodeID    = Bits_Timestamp + Bits_Sequence // 节点ID移位
	Shift_Timestamp = Bits_Sequence                  // 时间戳移位
	Shift_Sequence  = 0                              // 序号移位

	Mask_NodeID    = Max_NodeID << Shift_NodeID       // 节点ID掩码
	Mask_Timestamp = Max_Timestamp << Shift_Timestamp // 时间戳掩码
	Mask_Sequence  = Max_Sequence << Shift_Sequence   // 序号掩码

	DefaultStr = "123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
)

/*
	GUID二进制位分布如下:
	 		------------------------------------------
	fileds  | Symbol | NodeID | Timestamp | Sequence |
	 		------------------------------------------
	bytes   |   1    |   10   |     33    |    20    |
	 		------------------------------------------
*/

// UUID生成器
type UuidGen struct {
	mu sync.Mutex

	// 节点ID
	nodeID uint64

	// 源字符串
	sourceStr string

	// 上次分配的序列号
	sequence uint64

	// 上次分配时间
	timestamp uint64
}

// 序列号分配完毕或系统时钟回拨时,循环等待系统时钟增长到上次分配时钟之后
func (gen *UuidGen) wait(old uint64) uint64 {
	for {
		now := uint64(time.Now().Unix())
		if now > old {
			return now
		}
		time.Sleep(time.Millisecond)
	}
}

// 获取参数
func (gen *UuidGen) getArgs() (uint64, uint64, uint64) {
	gen.mu.Lock()
	defer gen.mu.Unlock()

	// 时间戳
	timestamp := uint64(time.Now().Unix())
	if timestamp < gen.timestamp {
		timestamp = gen.wait(gen.timestamp)
	}

	// 序号
	if timestamp == gen.timestamp {
		gen.sequence++
		if gen.sequence > Max_Sequence {
			gen.sequence = 0
			timestamp = gen.wait(timestamp)
		}
	} else {
		gen.sequence = 0
	}

	if timestamp > Max_Timestamp {
		log.Panicf("timestamp %v overflow %v", timestamp, Max_Timestamp)
	}

	gen.timestamp = timestamp

	return gen.nodeID, gen.timestamp, gen.sequence
}

// 根据提供的参数生成唯一ID
func (gen *UuidGen) GenUint64FromArgs(nodeID, timestamp, sequence uint64) uint64 {
	bitsNodeID := (nodeID << Shift_NodeID) & Mask_NodeID
	bitsTimestamp := (timestamp << Shift_Timestamp) & Mask_Timestamp
	bitsSequence := (sequence << Shift_Sequence) & Mask_Sequence
	return bitsNodeID | bitsTimestamp | bitsSequence
}

// 生成数字唯一ID
func (gen *UuidGen) GenUint64() uint64 {
	return gen.GenUint64FromArgs(gen.getArgs())
}

// 解码数字唯一ID
func (gen *UuidGen) DecodeUint64(guid uint64) (uint64, uint64, uint64) {
	nodeID := (guid & Mask_NodeID) >> Shift_NodeID
	timestamp := (guid & Mask_Timestamp) >> Shift_Timestamp
	sequence := (guid & Mask_Sequence) >> Shift_Sequence
	return nodeID, timestamp, sequence
}

// 根据数字生成字符串唯一ID
func (gen *UuidGen) GenStringFromUint64(num uint64) string {
	list := []byte{}
	length := uint64(len(gen.sourceStr))
	for num > 0 {
		mod := num % length
		num = (num - mod) / length
		list = append(list, gen.sourceStr[mod])
	}

	return string(list)
}

// 解码字符串为对应数字
func (gen *UuidGen) DecodeStringToUint64(str string) (num uint64, err error) {
	length := uint64(len(gen.sourceStr))
	for i := 0; i < len(str); i++ {
		index := strings.IndexByte(gen.sourceStr, str[i])
		if index == -1 {
			err = fmt.Errorf("\"%s\" have byte '%s' not found in \"%s\"", str, string(str[i]), gen.sourceStr)
		}
		num += uint64(index) * uint64(math.Pow(float64(length), float64(i)))
	}

	return
}

// 生成字符串唯一ID
func (gen *UuidGen) GenString() string {
	return gen.GenStringFromUint64(gen.GenUint64())
}

// 解码字符串唯一ID
func (gen *UuidGen) DecodeString(str string) (nodeID uint64, timestamp uint64, sequence uint64, err error) {
	num, err := gen.DecodeStringToUint64(str)
	if err == nil {
		nodeID, timestamp, sequence = gen.DecodeUint64(num)
	}

	return
}

// 创建UUID生成器对象
func NewUuidGen(nodeID uint64, sourceStr string) *UuidGen {
	if nodeID > Max_NodeID {
		log.Panicf("node id %v overflow %v", nodeID, Max_NodeID)
	}

	if sourceStr == "" {
		sourceStr = DefaultStr
	}

	gen := new(UuidGen)
	gen.nodeID = nodeID
	gen.sourceStr = sourceStr
	gen.timestamp = uint64(time.Now().Unix())

	return gen
}
